{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "c:\\Reaserch\\Code\\CEM\\AntennaGPT\\code\\Patch_Antenna.cst\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  0%|          | 0/499 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OBJ 文件已生成：../data/data1.obj\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  0%|          | 0/499 [01:08<?, ?it/s]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import triangle\n",
    "import numpy as np\n",
    "from shapely.geometry import Polygon\n",
    "import matplotlib.pyplot as plt\n",
    "import os\n",
    "import numpy as np\n",
    "import sys\n",
    "sys.path.append(r\"C:\\Reaserch\\CSTStudioSuite2021\\AMD64\\python_cst_libraries\")\n",
    "import cst.interface\n",
    "\n",
    "import trimesh\n",
    "import plotly.graph_objects as go\n",
    "from tqdm import tqdm\n",
    "line_break = '\\n'#换行符，后面用于VBA代买的拼接用P\n",
    "\n",
    "\n",
    "# 生成一个随机的二维多边形\n",
    "def generate_random_2d_polygon(num_vertices=6):\n",
    "    #points = np.random.rand(num_vertices, 2)\n",
    "    points = np.random.uniform(-19, 19,size=(num_vertices, 2))\n",
    "    center = np.mean(points, axis=0)\n",
    "    angles = np.arctan2(points[:, 1] - center[1], points[:, 0] - center[0])\n",
    "    points = points[np.argsort(angles)]\n",
    "    return points\n",
    "\n",
    "\n",
    "# 生成包含已知边的随机二维多边形（y坐标要大于0.4或者小于0.3）\n",
    "def generate_polygon_with_fixed_edges(fixed_edges, num_random_vertices=4):\n",
    "    # 提取固定边的顶点\n",
    "    fixed_points = np.vstack(fixed_edges)\n",
    "    \n",
    "    # 随机生成其余的点，满足 y > 0.4 或 y < 0.3\n",
    "    random_points = []\n",
    "    while len(random_points) < num_random_vertices:\n",
    "        #point = np.random.rand(2)  # 生成一个随机点\n",
    "        point = np.random.uniform(-19, 19,size=2)\n",
    "        #if point[1] > 0.4 or point[1] < 0.3:  # 仅保留 y 坐标符合条件的点\n",
    "        random_points.append(point)\n",
    "    random_points = np.array(random_points)\n",
    "    \n",
    "    # 将固定的点和随机点合并在一起\n",
    "    all_points = np.vstack([fixed_points, random_points])\n",
    "    \n",
    "    # 计算所有点的中心点\n",
    "    center = np.mean(all_points, axis=0)\n",
    "    \n",
    "    # 按照与中心点的角度对所有点进行排序（确保按逆时针方向排列）\n",
    "    angles = np.arctan2(all_points[:, 1] - center[1], all_points[:, 0] - center[0])\n",
    "    sorted_points = all_points[np.argsort(angles)]\n",
    "    \n",
    "    # 使用 shapely 生成 Polygon\n",
    "    polygon = Polygon(sorted_points)\n",
    "    \n",
    "    return polygon, sorted_points\n",
    "# 对多边形进行三角剖分\n",
    "def constrained_triangulation(polygon_points):\n",
    "    vertices = np.array(polygon_points)\n",
    "    # 定义约束边，保持多边形的边界不被破坏\n",
    "    segments = np.array([[i, (i + 1) % len(vertices)] for i in range(len(vertices))])\n",
    "    \n",
    "    # 使用 triangle 库进行带约束的三角剖分\n",
    "    A = dict(vertices=vertices, segments=segments)\n",
    "    triangulation = triangle.triangulate(A, 'p')\n",
    "    \n",
    "    return triangulation\n",
    "\n",
    "# 将三角剖分结果保存为OBJ文件\n",
    "def export_to_obj(triangulation, filename=\"polygon_triangulated.obj\"):\n",
    "    vertices = triangulation['vertices']\n",
    "    triangles = triangulation['triangles']\n",
    "    \n",
    "    with open(filename, 'w') as f:\n",
    "        # 写入顶点信息\n",
    "        for v in vertices:\n",
    "            f.write(f\"v {v[0]} {v[1]} 0.0\\n\")  # z 设为 0 表示在二维平面上\n",
    "        \n",
    "        # 写入面（每个面由3个顶点构成）\n",
    "        for tri in triangles:\n",
    "            # OBJ 索引从1开始，因此要加1\n",
    "            f.write(f\"f {tri[0] + 1} {tri[1] + 1} {tri[2] + 1}\\n\")\n",
    "\n",
    "\n",
    "# 加载OBJ文件并转换为Plotly格式\n",
    "def plot_obj_in_jupyter(obj_filename):\n",
    "    # 使用trimesh加载OBJ文件\n",
    "    mesh = trimesh.load(obj_filename)\n",
    "    \n",
    "    # 获取顶点和面数据\n",
    "    vertices = mesh.vertices\n",
    "    faces = mesh.faces\n",
    "    \n",
    "    # 创建Plotly图形对象\n",
    "    mesh_plot = go.Mesh3d(\n",
    "        x=vertices[:, 0],\n",
    "        y=vertices[:, 1],\n",
    "        z=vertices[:, 2],\n",
    "        i=faces[:, 0],\n",
    "        j=faces[:, 1],\n",
    "        k=faces[:, 2],\n",
    "        opacity=0.5,\n",
    "        color='lightblue'\n",
    "    )\n",
    "    \n",
    "    # 设置布局\n",
    "    layout = go.Layout(\n",
    "        scene=dict(\n",
    "            xaxis=dict(visible=False),\n",
    "            yaxis=dict(visible=False),\n",
    "            zaxis=dict(visible=False)\n",
    "        ),\n",
    "        title=\"OBJ File Visualization\"\n",
    "    )\n",
    "    \n",
    "    # 创建并展示图像\n",
    "    fig = go.Figure(data=[mesh_plot], layout=layout)\n",
    "    fig.show()\n",
    "\n",
    "def cst_init(fullname,modeler,mws):\n",
    "    # path = os.getcwd()#获取当前py文件所在文件夹\n",
    "    # filename = 'Patch_Antenna.cst'\n",
    "    # fullname = os.path.join(path,filename)\n",
    "    print(fullname)\n",
    "\n",
    "    # project = cst.interface.DesignEnvironment()\n",
    "    # project.set_quiet_mode(False)\n",
    "    # mws = project.new_mws()\n",
    "    mws.save(fullname)\n",
    "    # modeler = mws.modeler\n",
    "    #贴片天线建模基本参数\n",
    "    a = 38.6 #贴片长\n",
    "    b = 38 #贴片宽\n",
    "    w = 1.46 #馈线宽，100欧姆传输线\n",
    "    l = 40 #馈线长\n",
    "    lx = 100 #基板长\n",
    "    ly = 100 #基板宽\n",
    "    ts = 2 #基板厚\n",
    "    tm = 0.035 #金属层厚\n",
    "    Frq = [2,10] #工作频率，单位：GHz\n",
    "\n",
    "    #在CST中加入结构参数，方便后续手动在CST文件中进行操作\n",
    "    modeler.add_to_history('StoreParameter','MakeSureParameterExists(\"a\", \"%f\")' % a)\n",
    "    modeler.add_to_history('StoreParameter','MakeSureParameterExists(\"b\", \"%f\")' % b)\n",
    "    modeler.add_to_history('StoreParameter','MakeSureParameterExists(\"w\", \"%f\")' % w)\n",
    "    modeler.add_to_history('StoreParameter','MakeSureParameterExists(\"l\", \"%f\")' % l)\n",
    "    modeler.add_to_history('StoreParameter','MakeSureParameterExists(\"lx\", \"%f\")' % lx)\n",
    "    modeler.add_to_history('StoreParameter','MakeSureParameterExists(\"ly\", \"%f\")' % ly)\n",
    "    modeler.add_to_history('StoreParameter','MakeSureParameterExists(\"ts\", \"%f\")' % ts)\n",
    "    modeler.add_to_history('StoreParameter','MakeSureParameterExists(\"tm\", \"%f\")' % tm)\n",
    "    #建模基本参数设置结束\n",
    "    line_break = '\\n'#换行符，后面用于VBA代买的拼接用\n",
    "    #全局单位初始化\n",
    "    sCommand = ['With Units',\n",
    "                '.Geometry \"mm\"',\n",
    "                '.Frequency \"ghz\"',\n",
    "                '.Time \"ns\"',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)\n",
    "    modeler.add_to_history('define units', sCommand)\n",
    "    #全局单位初始化结束\n",
    "\n",
    "    #工作频率设置\n",
    "    sCommand = 'Solver.FrequencyRange \"%f\",\"%f\"'  % (Frq[0],Frq[1])\n",
    "    modeler.add_to_history('define frequency range', sCommand)\n",
    "    #工作频率设置结束\n",
    "\n",
    "    #背景材料设置\n",
    "    sCommand = ['With Background',\n",
    "                '.ResetBackground',\n",
    "                '.Type \"Normal\"',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)\n",
    "    modeler.add_to_history('define background', sCommand)\n",
    "    #背景材料设置结束\n",
    "\n",
    "    #边界条件设置。\n",
    "    sCommand = ['With Boundary',\n",
    "                '.Xmin \"expanded open\"',\n",
    "                '.Xmax \"expanded open\"',\n",
    "                '.Ymin \"expanded open\"',\n",
    "                '.Ymax \"expanded open\"',\n",
    "                '.Zmin \"expanded open\"',\n",
    "                '.Zmax \"expanded open\"',\n",
    "                '.Xsymmetry \"none\"',\n",
    "                '.Ysymmetry \"none\"',\n",
    "                '.Zsymmetry \"none\"',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)\n",
    "    modeler.add_to_history('define boundary', sCommand)\n",
    "    #边界条件设置结束\n",
    "\n",
    "    #新建所需介质材料\n",
    "    er1 = 2.65\n",
    "    sCommand = ['With Material',\n",
    "                '.Reset',\n",
    "                '.Name \"material1\"',\n",
    "                '.FrqType \"all\"',\n",
    "                '.Type \"Normal\"',\n",
    "                '.Epsilon %f' %er1,\n",
    "                '.Create',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)\n",
    "    modeler.add_to_history('define material: material265', sCommand)\n",
    "    #新建所需介质材料结束\n",
    "\n",
    "    #使Bounding Box显示\n",
    "    sCommand = 'Plot.DrawBox \"True\"'\n",
    "    modeler.add_to_history('switch bounding box', sCommand)\n",
    "    #使Bounding Box显示结束\n",
    "\n",
    "    sCommand = 'Plot.ZoomToStructure'\n",
    "    modeler.add_to_history('ZoomToStructure', sCommand)#缩放到适合大小，就和在CST里面按空格是一个效果\n",
    "\n",
    "    Str_Name='line1'\n",
    "    Str_Component='Feed'\n",
    "    Str_Material='PEC'\n",
    "    sCommand = ['With Brick',\n",
    "                '.Reset',\n",
    "                '.Name \"%s\"' % Str_Name,\n",
    "                '.Component \"%s\"' % Str_Component,\n",
    "                '.Material \"%s\"' % Str_Material,\n",
    "                '.Xrange \"-lx/2\",\"-a/2\"',\n",
    "                '.Yrange \"-w/2\",\"w/2\"',\n",
    "                '.Zrange \"0\",\"tm\"',\n",
    "                '.Create',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)\n",
    "    modeler.add_to_history('define brick:%s:%s' % (Str_Component,Str_Name,), sCommand) \n",
    "\n",
    "    Str_Name='line2'\n",
    "    Str_Component='Feed'\n",
    "    Str_Material='PEC'\n",
    "    sCommand = ['With Brick',\n",
    "                '.Reset',\n",
    "                '.Name \"%s\"' % Str_Name,\n",
    "                '.Component \"%s\"' % Str_Component,\n",
    "                '.Material \"%s\"' % Str_Material,\n",
    "                '.Xrange \"a/2\",\"lx/2\"',\n",
    "                '.Yrange \"-w/2\",\"w/2\"',\n",
    "                '.Zrange \"0\",\"tm\"',\n",
    "                '.Create',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)\n",
    "    modeler.add_to_history('define brick:%s:%s' % (Str_Component,Str_Name,), sCommand)  \n",
    "    Str_Name='bottom'\n",
    "    Str_Component='Bottom'\n",
    "    Str_Material='PEC'\n",
    "    sCommand = ['With Brick',\n",
    "                '.Reset',\n",
    "                '.Name \"%s\"' % Str_Name,\n",
    "                '.Component \"%s\"' % Str_Component,\n",
    "                '.Material \"%s\"' % Str_Material,\n",
    "                '.Xrange \"-lx/2\",\"lx/2\"',\n",
    "                '.Yrange \"-ly/2\",\"ly/2\"',\n",
    "                '.Zrange \"-ts-tm\",\"-ts\"',\n",
    "                '.Create',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)\n",
    "    modeler.add_to_history('define brick:%s:%s' % (Str_Component,Str_Name,), sCommand) \n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "    Str_Name='sub'\n",
    "    Str_Component='Sub'\n",
    "    Str_Material='material1'\n",
    "    sCommand = ['With Brick',\n",
    "                '.Reset',\n",
    "                '.Name \"%s\"' % Str_Name,\n",
    "                '.Component \"%s\"' % Str_Component,\n",
    "                '.Material \"%s\"' % Str_Material,\n",
    "                '.Xrange \"-lx/2\",\"lx/2\"',\n",
    "                '.Yrange \"-ly/2\",\"ly/2\"',\n",
    "                '.Zrange \"-ts\",\"0\"',\n",
    "                '.Create',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)\n",
    "    modeler.add_to_history('define brick:%s:%s' % (Str_Component,Str_Name,), sCommand)\n",
    "    #建模结束\n",
    "\n",
    "    sCommand = 'Plot.ZoomToStructure'\n",
    "    modeler.add_to_history('ZoomToStructure', sCommand)#缩放到适合大小，就和在CST里面按空格是一个效果\n",
    "\n",
    "    #端口设置，采用的方法和在CST里面选中一个面然后设置端口是一样的操作，这里完全复现\n",
    "    #端口1\n",
    "    sCommand = 'Pick.PickFaceFromId \"Feed:line1\",4'\n",
    "    modeler.add_to_history('pick face', sCommand)\n",
    "    sCommand = ['With Port',\n",
    "                '.Reset',\n",
    "                '.PortNumber 1',\n",
    "                '.Label  \"\"',\n",
    "                '.NumberOfModes 1',\n",
    "                '.AdjustPolarization \"False\"',\n",
    "                '.PolarizationAngle 0.0',\n",
    "                '.ReferencePlaneDistance 0',\n",
    "                '.TextSize 50',\n",
    "                '.TextMaxLimit 0',\n",
    "                '.Coordinates \"Picks\"',\n",
    "                '.Orientation \"positive\"',\n",
    "                '.PortOnBound \"False\"',\n",
    "                '.ClipPickedPortToBound \"False\"',\n",
    "                '.Xrange \"-lx/2\",\"-lx/2\"',\n",
    "                '.Yrange \"-w/2\",\"w/2\"',\n",
    "                '.Zrange \"0\",\"tm\"',\n",
    "                '.XrangeAdd \"0.0\",\"0.0\"',\n",
    "                '.YrangeAdd \"3*ts\",\"3*ts\"',\n",
    "                '.ZrangeAdd \"ts\",\"3*ts\"',\n",
    "                '.SingleEnded \"False\"',\n",
    "                '.Create',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)     \n",
    "    modeler.add_to_history('define port1', sCommand)\n",
    "\n",
    "    #端口2\n",
    "    sCommand = 'Pick.PickFaceFromId \"Feed:line2\",6'\n",
    "    modeler.add_to_history('pick face', sCommand)\n",
    "    sCommand = ['With Port',\n",
    "                '.Reset',\n",
    "                '.PortNumber 2',\n",
    "                '.Label  \"\"',\n",
    "                '.NumberOfModes 1',\n",
    "                '.AdjustPolarization \"False\"',\n",
    "                '.PolarizationAngle 0.0',\n",
    "                '.ReferencePlaneDistance 0',\n",
    "                '.TextSize 50',\n",
    "                '.TextMaxLimit 0',\n",
    "                '.Coordinates \"Picks\"',\n",
    "                '.Orientation \"positive\"',\n",
    "                '.PortOnBound \"False\"',\n",
    "                '.ClipPickedPortToBound \"False\"',\n",
    "                '.Xrange \"lx/2\",\"lx/2\"',\n",
    "                '.Yrange \"-w/2\",\"w/2\"',\n",
    "                '.Zrange \"0\",\"tm\"',\n",
    "                '.XrangeAdd \"0.0\",\"0.0\"',\n",
    "                '.YrangeAdd \"3*ts\",\"3*ts\"',\n",
    "                '.ZrangeAdd \"ts\",\"3*ts\"',\n",
    "                '.SingleEnded \"False\"',\n",
    "                '.Create',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)     \n",
    "    modeler.add_to_history('define port2', sCommand)\n",
    "    #端口设置结束\n",
    "\n",
    "    #设置远场方向图的Monitor\n",
    "    sCommand = ['With Monitor',\n",
    "                '.Reset',\n",
    "                '.Domain \"Frequency\"',\n",
    "                '.FieldType \"Farfield\"',\n",
    "                '.ExportFarfieldSource \"True\"',\n",
    "                '.UseSubvolume \"False\"',\n",
    "                '.Coordinates \"Picks\"',\n",
    "                '.SetSubvolume \"50\", \"50\", \"-0.73\", \"0.73\", \"0\", \"0.035\"',\n",
    "                '.SetSubvolumeOffset \"10\", \"10\", \"10\", \"10\", \"10\", \"10\" ',\n",
    "                '.SetSubvolumeInflateWithOffset \"False\" ',\n",
    "                '.SetSubvolumeOffsetType \"FractionOfWavelength\" ',\n",
    "                '.EnableNearfieldCalculation \"True\" ',\n",
    "                '.CreateUsingLinearStep \"%f\", \"%f\", \"%f\"' % (Frq[0],Frq[1],0.05),\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand) \n",
    "    modeler.add_to_history('define farfield monitor (using linear step)',sCommand)\n",
    "    #设置远场方向图的Monitor结束\n",
    "\n",
    "def cst_import_obj(modeler,file_path):\n",
    "    #file_path=\"C:\\\\Reaserch\\\\Code\\\\CEM\\\\AntennaGPT\\\\code\\\\polygon_triangulated3.obj\"\n",
    "    Str_Name='patch'\n",
    "    Str_Component='Patch'\n",
    "    sCommand = ['With OBJ',\n",
    "                '.Reset',\n",
    "                '.FileName (\"%s\")' % file_path,\n",
    "                '.Name (\"test\")',\n",
    "                '.Layer (\"Patch\")',\n",
    "                '.ScaleFactor (\"1\")',\n",
    "                '.ImportToActiveCoordinateSystem (False)',\n",
    "                '.Read',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)\n",
    "    modeler.add_to_history('Import OBJ File', sCommand)\n",
    "    # pick一个face\n",
    "    sCommand = 'Pick.PickFaceFromId \"Patch:test\",0'\n",
    "    modeler.add_to_history('pick face', sCommand)\n",
    "\n",
    "    #  Extrude Face\n",
    "    sCommand = ['With Extrude',\n",
    "            '.Reset',\n",
    "            '.Name (\"solid2\")',\n",
    "            '.Component (\"component1\")',\n",
    "            '.Material (\"PEC\")',\n",
    "            '.Mode (\"Picks\")',\n",
    "            '.Height (\"tm\")',\n",
    "            '.Taper (5)',\n",
    "            '.UsePicksForHeight (False)',\n",
    "            '.DeleteBaseFaceSolid (False)',\n",
    "            '.ClearPickedFace (True)',\n",
    "            '.Create',\n",
    "            'End With']\n",
    "    sCommand = line_break.join(sCommand)\n",
    "    modeler.add_to_history('Extrude', sCommand)\n",
    "def cst_delete_compo(modeler):\n",
    "    sCommand = 'Component.Delete \"component1\"'\n",
    "    modeler.add_to_history('pick face', sCommand)\n",
    "    sCommand = 'Component.Delete \"Patch\"'\n",
    "    modeler.add_to_history('pick face', sCommand)\n",
    "def cst_run(modeler,mws,fullname):\n",
    "    #仿真开始\n",
    "    modeler. run_solver()\n",
    "    #仿真结束\n",
    "\n",
    "    mws.save(fullname)#保存\n",
    "def cst_export_s(modeler,file_path):\n",
    "    # 导出\n",
    "    sCommand = ['With TOUCHSTONE',\n",
    "                '.Reset',\n",
    "                '.FileName (\"%s\")' %(file_path),\n",
    "                '.Impedance (50)',\n",
    "                '.FrequencyRange (\"Full\")',\n",
    "                '.Renormalize (False)',\n",
    "                '.UseARResults (False)',\n",
    "                '.SetNSamples (1001)',\n",
    "                '.Format (\"DB\")',\n",
    "                '.Write',\n",
    "                'End With']\n",
    "    sCommand = line_break.join(sCommand)\n",
    "    modeler.add_to_history('Export', sCommand)\n",
    "\n",
    "# 主程序\n",
    "if __name__ == \"__main__\":\n",
    "    # 路径\n",
    "    file='../data/'\n",
    "\n",
    "    path = os.getcwd()\n",
    "    filename = 'Patch_Antenna.cst'\n",
    "    fullname = os.path.join(path,filename)\n",
    "\n",
    "    # 假设有两条固定的边\n",
    "    fixed_edge_1 = np.array([[19.3, 0.73], [19.3, -0.73]])  # 第一条固定边\n",
    "    fixed_edge_2 = np.array([[-19.3, 0.73], [-19.3, -0.73]])  # 第二条固定边\n",
    "\n",
    "    #polygon_points = generate_random_2d_polygon(num_vertices)\n",
    "\n",
    "    # 生成一个随机多边形，包含固定的边，且随机点的y坐标满足要求\n",
    "    project = cst.interface.DesignEnvironment()\n",
    "    project.set_quiet_mode(False)\n",
    "    mws = project.new_mws()\n",
    "    modeler = mws.modeler\n",
    "\n",
    "    cst_init(fullname,modeler,mws)\n",
    "    for i in tqdm(range(1,500)):\n",
    "        file_obj=file+'data'+str(i)+'.obj'\n",
    "        file_s2p=file+'data'+str(i)\n",
    "        \n",
    "\n",
    "        polygon, sorted_points = generate_polygon_with_fixed_edges([fixed_edge_1, fixed_edge_2], num_random_vertices=5)\n",
    "\n",
    "        # 进行带约束的三角剖分\n",
    "        triangulation = constrained_triangulation(sorted_points)\n",
    "        \n",
    "        # 导出为OBJ文件\n",
    "        export_to_obj(triangulation,file_obj)\n",
    "        \n",
    "        print(\"OBJ 文件已生成：\"+file_obj)\n",
    "        # 展示生成的 OBJ 文件\n",
    "        #plot_obj_in_jupyter(file_obj)\n",
    "\n",
    "        cst_import_obj(modeler,os.path.abspath(file_obj))\n",
    "        cst_run(modeler,mws,fullname)\n",
    "        cst_export_s(modeler,os.path.abspath(file_s2p))\n",
    "        break\n",
    "        cst_delete_compo(modeler)\n",
    "    \n",
    "        \n",
    "        \n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
